package com.qtrue.thread;

/*
*   例子：创建四个窗口卖票，总票数为100张。（使用实现Runnable接口的方式实现）
*
*   1. 问题：卖票过程中出现了重票、错票 -> 出现了线程安全的问题
*   2. 原因：当某个线程操作车票的过程中，操作尚未完成时，其他线程参与进来，也操作车票。
*   3. 如何解决：当一个线程A在操作ticket（共享数据）的时候，其他线程不能参与进来，直到线程A操作完ticket的时候，
*               其他线程才可以开始操作ticket；这种情况即便线程A出现了阻塞，数据也不能被改变。
*
*   4. 在java中，通过同步机制来解决线程安全问题。
*       4.1 方式一：同步代码块
*
*           synchronized(线程同步锁){
*               // 需要被同步的代码
*           }
*           说明：操作共享数据的代码，即需要被同步的代码。（共享数据：多个线程共同操作的变量，如：ticket）
*           同步监视器：俗称“锁”。 任意一个类的对象，都可以用来充当“锁”。
*               要求：多个线程必须要共用同一把锁。
*           补充：在实现Runnable接口创建多个线程的方式中，可以考虑使用this充当同步锁（当线程类对象只new一次时可行）。
*
*       4.2 方式二：同步方法
*           如果操作共享数据的代码完整的声明在一个方法中，可以考虑将此方法声明为同步的。
*           1. 同步方法仍然涉及到线程同步锁，只是不需要我们显式的声明。
*           2. 非静态的同步方法，线程同步锁是：this（当前类对象）
*           3. 静态的同步方法，线程同步锁所：当前类本身（className.class）
*
*   5. 同步监视器的方式，解决了线程的安全问题  ---- 优点
*       操作同步代码时，只能有一个线程参与，其他线程等待。相当于是一个单线程的过程，效率低。 ---- 局限性
*/
public class WindowTest02 {
    public static void main(String[] args) {
        // 创建实现类的对象
        Window2 window = new Window2();

        // 将实现类对象作为参数传递到Thread类的构造器中
        // 以下创建的三个线程实际上是共用一个window对象，区别于第一种创建线程的方式（因此ticket属性不会出现三个窗口卖同一张票三次的情况）
        Thread thread01 = new Thread(window);
        Thread thread02 = new Thread(window);
        Thread thread03 = new Thread(window);
        Thread thread04 = new Thread(window);

        // 设置线程名称
        thread01.setName("窗口一");
        thread02.setName("窗口二");
        thread03.setName("窗口三");
        thread04.setName("窗口四");

        // 启动线程
        thread01.start();
        thread02.start();
        thread03.start();
        thread04.start();
    }
}

class Window2 implements Runnable {
    // 总票数
    private int ticket = 100;
//    Object obj = new Object(); // 声明一个对象作为线程同步锁

    @Override
    public void run() {

        while(true) {
//            synchronized(obj) { // 方式一
            /*
                方式二：这里的的this特指当前类对象Window2，将当前类的对象作为同步监视器。
                    注：将当前对象作为同步监视器的方法，在使用继承Thread类创建线程的方式时不可行。
                    原因：
                        1. 使用继承Thread类的方式，每启动一个线程都需要new一次线程类的对象，因此会出现多个线程不共用同一把锁的情况。
                        2. 使用实现Runnable接口创建线程的方式，只需要new一次线程类的对象，因此使用this作为锁时，多个线程使用的是同一把锁。
             */
            synchronized (this) {
                if (ticket > 0){
                    try {
                        // 线程休眠（这里用于观察线程的同步问题，当多个线程操作共享数据时，都会出现线程安全问题，通过sleep()来挂起线程，可以更直观的看到问题）
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    System.out.println(Thread.currentThread().getName() + "：卖票，票号为：" + ticket);

                    ticket--;
                }else {
                    break;
                }
            }
        }

    }
}
